<template>
  <div class="app">
    <!-- 聊天记录展示部分 -->
    <div class="chat-messages">
      <div ref="scrollContainer" style="height: calc(100vh - 220px); overflow: auto; width: 100%">
        <div v-if="chatArr!== undefined && chatArr.length >= 1" v-for="(item, index) in chatArr" :key="index" class="chatarr">
          <!-- AI回答部分渲染 -->
          <div v-if="item.role === 'assistant'" class="reply0">
            <div class="avatar"><img width="35" src="../../../../assets/images/chatgpt.png" alt=""></div>
            <div class="contentmessage">
              <MdPreview :editorId="id" :modelValue="item.content"/>  
              <MdCatalog :editorId="id" />
              <!-- 显示知识库来源部分(条件渲染) -->
              <div v-if="item.chatType == 1" class="knowledegelabel">
                <div v-if="item.list?.length" class="source">
                  <a-tag :bordered="false" style="background: rgba(97,92,237,0.2); color:#615ced;" @click="showModal(item)">
                    来源:{{ item.list[0].source }}等
                  </a-tag>
                  <!-- 文件来源弹窗 -->
                  <a-modal 
                    v-model:open="open" 
                    width="1000px" 
                    :bodyStyle="{height:'500px', overflow:'auto'}" 
                    title="知识库来源"
                    footer=""
                  >
                    <div v-for="(li, index) in currentItem" style="margin-bottom: 2px;border-top: 2px solid #f7f7f7">
                      <div style="padding-top:8px; padding-bottom:8px; font-size:15px; font-weight: 600;" class="source">
                        {{index+1 }}.文档出处：
                        <a-tag color="#615ced" @click="handleDownload(li.docId)" style="background: rgba(97,92,237,0.2); color:#615ced;">
                        <template #icon>
                          <FileWordOutlined />
                        </template>
                        <span class="liSource">
                          {{ li.source }}
                        </span>
                        </a-tag>                      
                      </div>
                      <div style="padding-bottom:8px; font-size:15px; font-weight: 600; padding-left: 11px;">
                        文档内容：
                      </div>
                      <div style="padding-left:11px">{{ li.pageContent }}</div>     
                    </div>
                  </a-modal>                  
                </div>
                <div v-else class="source">
                  <a-tag :bordered="false" color="error">
                    来源: 本内容并非来源于知识库，由AI自动生成，请注意甄别！
                  </a-tag>                  
                </div>
                <div class="like" style="color: darkgrey;">
                  <LikeOutlined style="color: darkgrey; padding-left: 8px; padding-right: 8px;"/>|
                  <DislikeOutlined 
                  style="color: darkgrey; 
                  padding-left: 8px; 
                  padding-right: 8px;"
                  />|
                  <NotificationOutlined
                    style="
                    color: darkgrey; 
                    padding-left: 8px;
                    "
                    @click="getTtsChunkWavWS(item)"
                  />               
                </div>
              </div>
              <div v-else>
                <div class="like" style="color: darkgrey;">
                  <LikeOutlined style="color: darkgrey; padding-left: 8px; padding-right: 8px;"/>|
                  <DislikeOutlined 
                  style="color: darkgrey; 
                  padding-left: 8px; 
                  padding-right: 8px;"
                  />|
                  <NotificationOutlined
                    style="
                    color: darkgrey; 
                    padding-left: 8px;
                    "
                    @click="getTtsChunkWavWS(item)"
                  />               
                </div>
              </div>
              
            </div> 
          </div>
          <!-- 用户提问部分渲染 -->
          <div v-if="item.role === 'user'" class="reply1">
            <div class="avatar"><img width="35" src="../../../../assets/images/user.jpg" alt=""></div>
            <div class="contentmessage">{{ item.content }}</div>
          </div>
        </div>
        <div v-else class="chatarr" style="height: calc(100vh - 240px)">
          <div class="logodisplay">
            <div><img width="50"  src="/logo.png" alt="" srcset=""></div>
            <div class="infofont">你好，我是EAI助手i华电</div>
            <div class="infoefont">我能理解人类语言、文档阅读、生成内容，是你生活和工作的智能助手。</div>
          </div>
        </div>
        <Affix :offset-bottom="0">
          <div style="height: 20px;"></div>
        </Affix>
      </div>
    </div>
    <!-- 输入框部分 -->
    <div class="chat-input">
      <div class="coalesce">
        <div class="selectKnowledge">
          <div class="selectdiv">
            <div class="checkbox">
              <a-checkbox v-model:checked="checked" class="fontstyle">知识库</a-checkbox>
            </div>
            <div v-if="checked === true" class="selectmatch">
              知识库名称:            
              <a-select
                v-model:value="knowledgeBaseId"
                class="selectstyle"
                :dropdown-match-select-width="false"
                :placement="placement"
              >
                <a-select-option 
                  v-for="item in knowledge" 
                  :value="item.id"
                  :key="item.knowledgeBaseName"
                >
                  {{ item.knowledgeBaseName }}
                </a-select-option>
              </a-select>
            </div>          
          </div>
        </div>
        <a-textarea 
          class="textarea" 
          v-model:value="prompt" 
          placeholder="可通过 shift + 回车换行"
          :auto-size="{ minRows: 4, maxRows: 8 }" 
          style="over-flow-y:auto"
          @keydown.enter="handleEnter"
          @keydown.enter.shift.prevent="handleShiftEnter" />
          <a-tooltip v-if="onReco" color="#6675d3">
            <template #title>结束录音</template>
            <a-button
              v-if="onReco"
              @click="endRecorder"
              class="audio-button"
              :disabled="audioing"
              :loading="audioing"
              >
              <LoadingOutlined />
            </a-button>
          </a-tooltip>
          <a-tooltip color="#6675d3">
            <template v-if="!onReco" #title>开始录音</template>
            <a-button
              v-if="!onReco"
              @click="startRecorder"
              class="audio-button"
              :disabled="audioing"
              :loading="audioing"
              >
              <AudioOutlined />
            </a-button>
          </a-tooltip>

        <!-- 发送按钮 -->
        <a-button 
        @click="sendMessage" 
        class="send-button"
        :disabled="prompt.trim() === '' || loading"
        :loading="loading"
        >
          <SendOutlined />
        </a-button>        
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import type { Knowledge } from '@/api/common/user'
import type { knowledgeList } from '@/api/dashboard/chat'
import { addConversion, docList, findKnowledgeById, findDownloadUrl } from '@/api/dashboard/chat'
import { chatChatStore } from '@/stores/chatmessage'
import { useUserStore } from '@/stores/user'
import { 
  DislikeOutlined, 
  LikeOutlined, 
  SendOutlined, 
  SnippetsFilled, 
  EyeOutlined,
  FileWordFilled,
  FileWordOutlined,
  AudioOutlined, 
  LoadingOutlined,
  NotificationOutlined,
  DownloadOutlined,
} from '@ant-design/icons-vue'
import { Affix, message as Message } from 'ant-design-vue'
import axios from 'axios'
import { MdCatalog, MdPreview } from 'md-editor-v3'
import 'md-editor-v3/lib/style.css'
import moment from 'moment'
import { computed, onMounted, onUpdated, ref } from 'vue'
import router from '~@/router'
import Recoder from 'js-audio-recorder'
import { audioApi } from '@/api/dashboard/audio'
import { DOWNLOADURL } from '@/utils/constant'

const userStore = useUserStore()
const chatStore = chatChatStore()
const baseUrl = import.meta.env.VITE_APP_BASE_API

const scrollContainer = ref<HTMLDivElement | null>(null)
const knowledge = ref<Knowledge[]>([])
const prompt = ref<string>('')
const id = 'preview-only'
const loading = ref<boolean>(false);
const audioing = ref<boolean>(false)
const knowledgeBaseId = ref('')
const placement = ref('topLeft' as const);
const checked = ref(true);
const open = ref<boolean>(false);
const onReco = ref<boolean>(false)
const flag = ref<boolean>(false)
const asrResult = ref<string>('');
const audioplay = ref<boolean>(false)
const wsUrl = audioApi.ASR_SOCKET_RECORD;
const ws2Url = audioApi.TTS_SOCKET_RECORD;
let ws: WebSocket;
let intervalId: number;
let ws2: WebSocket;
// 承接全局流式chunk
let chunks = []
let chunk_index = 0
let reciveOver = false
let _audioSrcNodes = []
let mId = ref(0)


// 临时存储点击当前的知识库标题和内容
const currentItem = ref<knowledgeList[]>()

let chatArr = computed(() => chatStore.chatMessage)

const recorder = new Recoder({
  sampleBits: 16,      // 采样位数，支持 8 或 16，默认是16
  sampleRate: 16000,   // 采样率，支持 11025、16000、22050、24000、44100、48000，根据浏览器默认值，我的chrome是48000
  numChannels: 1,      // 声道，支持 1 或 2， 默认是1
  compiling: true
})

// 下滑列表始终保持在最新的对话位置(即最下方)
const scrollToBottom = () => {
  if (scrollContainer.value) {
    scrollContainer.value.scrollTop = scrollContainer.value.scrollHeight;
  }
};

// 展示知识库框
const showModal = (params:any) => {
  open.value = true;
  currentItem.value = params.list
};

// 请求获取知识库列表
async function getKnowledge(params:string) {
  const { data } = await findKnowledgeById(params)
  // Message.success('请选择知识库')
  knowledge.value = data.list
  // console.log(knowledge.value[0].id)
  if(knowledge.value[0].id)
  {
    knowledgeBaseId.value = knowledge.value[0].id
  }
}

// 监视是否选择知识库进行请求获取知识库列表
watchEffect(() => {
  const token = localStorage.getItem('Authorization')
  if(token && checked.value) {
    getKnowledge(token)
  }
})

onMounted(() => {
  scrollToBottom();
  ws = new WebSocket(wsUrl);
  ws2 = new WebSocket(ws2Url);
  ws.addEventListener('message', (event) => {
    const temp = JSON.parse(event.data)
    // console.log(temp)
    if(temp.result && temp.result!== asrResult.value) {
      asrResult.value = temp.result;
      prompt.value = asrResult.value
    }
  })
  // ws2.addEventListener('message',function(event) {
  //   console.log(event)
  //   console.log(audioplay)
  //   if(audioplay) {
  //     console.log(event)
  //     let temp = JSON.parse(event.data)
  //     /* if(chunk_index === 0){
  //       // 表示流式传输未停止
  //     } */
  //     // 接收的数据刷进播放器
  //     if(!temp.done) {
  //       chunk_index += 1 // 增加块索引
  //       let chunk = temp.wav // 获取音频数据块
  //       let arraybuffer = base64ToUint8Array(chunk)
  //       let view = new DataView(arraybuffer.buffer)

  //       let length = view.buffer.byteLength / 2

  //       view = Recoder.encodeWAV(view, 24000, 24000, 1, 16, true) // 对音频数据进行WAV格式编码
  //       // console.log('@@',typeof(view.buffer))
  //       console.log('1111')
  //       schedulePlaybackWav(view.buffer) // 将编码后的音频数据传入播放调度函数)
  //     } else {
  //       reciveOver = true //标记接收完成
  //     }      
  //   }
  // })
});

onUpdated(() => {
  scrollToBottom();
});

// 进行流式请求
async function onConConversation() {
  const message = prompt.value
  prompt.value = ''
  // 验证登录信息
  if (!userStore.userInfo || !localStorage.getItem('Authorization')) {
    Message.error('用户信息已失效，请重新登录')
    try {
      await userStore.logout()
    }
    finally {
      router.push({
        path: '/login',
      })      
    }
    return
  }
  let Index = chatStore.chatMessage.length - 1;
  let flag = true
  // chatStore.chatMessage[Index].content = ''
  // 如果是知识库对话
  if(checked.value && knowledgeBaseId.value) {
    // 进行知识库流式请求
    await axios({
      method:'post',
      url:baseUrl + '/chat/knowledge',
      data:{
        id: Number(userStore.userInfo.id),
        token: localStorage.getItem('Authorization'),
        chatId:Number(chatStore.getChatId()),
        content: message,
        knowledgeBaseId:knowledgeBaseId.value
      },
      headers:{
        Authorization:localStorage.getItem('Authorization'),
      },
      onDownloadProgress : ({event}) => {
        const xhr = event.target.responseText
        // 切行
        // console.log(xhr)
        let lines = xhr.split('\n\n')
        // 循环遍历每行，将data:去掉
        for (let i=0 ; i<lines.length; i++) {
          if(lines[i] == '\n') {
            continue
          }
          lines[i] = lines[i].replace('data:data:', '')      
          lines[i] = JSON.parse(lines[i]).text?.replace(/\\n/g,'\n')
        }
        // console.log(lines[lines.length - 1] == '\n')
        // 将处理后的行拼接起来
        let processedString = lines.join('')
        chatStore.chatMessage[Index].content = processedString
      }
    })
    // 获取命中文档信息
    const {data } = await docList(Number(chatStore.getChatId()))
    // 将命中文档信息加入最新一条chatMessage的list中
    chatStore.chatMessage[Index].list = data.list
    chatStore.chatMessage[Index].chatType = data.chatType
  } 
  // 如果不是知识库对话
  else {
    // 非知识库流式请求
    await axios({
      method:'post',
      url:baseUrl + '/chat/sendStreamMessage',
      data:{
        chatId: Number(chatStore.getChatId()),
        token: localStorage.getItem('Authorization'),
        id: Number(userStore.userInfo.id),
        content: message
      },
      headers:{
        Authorization:localStorage.getItem('Authorization'),
      },
      onDownloadProgress : ({event}) => {
        const xhr = event.target.responseText
        // 切行
        let lines = xhr.split('\n\n')
        // 循环遍历每行，将data:去掉
        for (let i=0 ; i<lines.length; i++) {
          if(lines[i] == '\n') {
            continue
          }
          lines[i] = lines[i].replace('data:data:', '')
          lines[i] = JSON.parse(lines[i]).text?.replace(/\\n/g,'\n')
          // console.log(lines[i])
        }
        // console.log(lines[lines.length - 1] == '\n')
        // 将处理后的行拼接起来
        let processedString = lines.join('')
        chatStore.chatMessage[Index].content = processedString
      }
    })    
  }

}

// 发送消息
async function sendMessage() {
  if(!chatStore.getChatId()) {
    if (!userStore.userInfo) {
        Message.error('未查到用户，请重新登录！');
        return
    }
    let { data } = await addConversion({
        id:Number(userStore.userInfo.id)
    })
    chatStore.updateChatId(data.chatId)
    chatStore.clearChatMessages()
  }
  if(loading.value) {
    Message.error('正在回答中，请稍后再发送...')
    return 
  }
  loading.value = true
  let chat = ref({
    chatId: Number(chatStore.getChatId()),
    content: prompt.value,
    createTime: moment().format('YYYY-MM-DD HH:mm:ss'),
    role: 'user'
  })

  chatStore.chatMessage.push(chat.value)
  let reply = ref({
    chatId: Number(chatStore.getChatId()),
    content: '思考中，请稍后...',
    createTime: moment().format('YYYY-MM-DD HH:mm:ss'),
    role: 'assistant'
  })
  chatStore.chatMessage.push(reply.value)
  try {
    await onConConversation()
    await chatStore.queryChat()
    loading.value = false
  } catch(error) {
    Message.error('请求失败')
  }
}

// 处理换行函数
const handleShiftEnter = (event: KeyboardEvent) => {
  if (event.shiftKey && event.key === 'Enter') {
    prompt.value += '\n';
  }
}

// 相关文件下载
async function handleDownload(docId:Number) {
  const token = localStorage.getItem('Authorization')
  const params = {
    docId:Number(docId),
    token:token
  }
  const res =await findDownloadUrl(params)
  // console.log(res)
  if(res.code === 200) {
    window.open(DOWNLOADURL + res.data.docUrl, '_blank')    
  } else {
    Message.error('下载请求失败，请重试！')
  }
}

// 语音上传
const startRecorder = () => {
  if (ws.readyState !== ws.OPEN) {
    console.error('websocket 链接失败，请检查 Websocket 后端服务是否正确开启');
    return;
  }

  onReco.value = true;
  const startMessage = JSON.stringify({ name: 'test.wav', nbest: 5, signal: 'start' });
  ws.send(startMessage);
  console.log(navigator.mediaDevices.getUserMedia)
  recorder.start().then(() => {
    intervalId = window.setInterval(() => {
      const newData = recorder.getNextData();
      if (newData.length) {
        uploadChunk(newData);
      }
    }, 300);
  }).catch((error:any) => {
    console.error('录音出错', error);
  });
};

const endRecorder = () => {
  recorder.stop();
  onReco.value = false;
  recorder.clear();
};

const uploadChunk = (chunkDatas: Uint8Array[]) => {
  chunkDatas.forEach((chunkData) => {
    ws.send(chunkData);
  });
};

// enter按钮事件
const handleEnter = (event: KeyboardEvent) => {
  if (loading.value) {
    Message.error('正在回答中，请稍后再发送...')
    return 
  }
  if (event.key === 'Enter' && !event.shiftKey) {
    event.preventDefault();
    sendMessage();
  }
}

// 端到端语音合成
async function EndToEndSynthesis(text:string | undefined){
  if(text) {
    let ttsResult = await axios.post('http://localhost:8011/api/tts/offline', { text: text})
    // console.log(ttsResult);
    
    if(ttsResult.status == 200) {
      let typedArray = base64ToUint8Array(ttsResult.data.result)
      // console.log(typeof(typedArray.buffer))
      schedulePlaybackWav(typedArray.buffer)
    }
  }
  
}

const base64ToUint8Array = (base64String:string) => {
    const padding = '='.repeat((4 - base64String.length % 4) % 4);
    const base64 = (base64String + padding)
                    .replace(/-/g, '+')
                    .replace(/_/g, '/');

    const rawData = window.atob(base64);
    const outputArray = new Uint8Array(rawData.length);

    for (let i = 0; i < rawData.length; ++i) {
            outputArray[i] = rawData.charCodeAt(i);
    }
    return outputArray;
};

const audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)();
const _audioCtx = new (window.AudioContext)({ latencyHint: 'interactive' })
const audioSrcNodes = ref<Array<AudioBufferSourceNode>>([]);
const playStartedAt = ref<number | null>(null);
const totalTimeScheduled = ref<number>(0);

const resetStatus = () => {   
  playStartedAt.value = null;
  totalTimeScheduled.value = 0;
  _audioSrcNodes = []
};

// 流式播放
const schedulePlaybackWav = (wavData:ArrayBuffer) => {
  // console.log('传入队列中...')
  /* if(!audioplay) {
    console.log('暂停')
    audioSrcNodes.value.forEach(node => node.stop());
    audioSrcNodes.value = [];
    return
  } */
  audioCtx.decodeAudioData(wavData, (audioBuffer) => {
    const audioSrc = audioCtx.createBufferSource();
    // 播放完毕时移除    
    audioSrc.onended = () => {
      audioSrcNodes.value.shift();
      if (audioSrcNodes.value.length === 0) {
        // 没有播放的语音了
        // audioplay.value = false
        // console.log(audioplay)
        // console.log('播放完毕')
        resetStatus();
      }
    };
    // 将音频源节点添加到数组中
    // console.log('audioSrc',audioSrc)
    audioSrcNodes.value.push(audioSrc);
    // console.log(audioSrcNodes.value,"@@")
    let startDelay = 0;
    if (!playStartedAt.value) {
      startDelay = 10 / 1000;
      playStartedAt.value = audioCtx.currentTime + startDelay;
    }
    audioSrc.buffer = audioBuffer;
    audioSrc.connect(audioCtx.destination);
    const startAt = playStartedAt.value + totalTimeScheduled.value;
    audioSrc.start(startAt);
    totalTimeScheduled.value += audioBuffer.duration;
  });
};

function handleMessage(event:any) {
    // console.log(audioplay);
    if (audioplay) {
        // console.log(event);
        let temp = JSON.parse(event.data);
        /* if(chunk_index === 0){
          // 表示流式传输未停止
        } */
        // 接收的数据刷进播放器
        if (!temp.done) {
            chunk_index += 1; // 增加块索引
            let chunk = temp.wav; // 获取音频数据块
            let arraybuffer = base64ToUint8Array(chunk);
            let view = new DataView(arraybuffer.buffer);

            let length = view.buffer.byteLength / 2;

            view = Recoder.encodeWAV(view, 24000, 24000, 1, 16, true); // 对音频数据进行WAV格式编码
            // console.log('@@',typeof(view.buffer))
            // console.log('1111');
            schedulePlaybackWav(view.buffer); // 将编码后的音频数据传入播放调度函数)
        } else {
            reciveOver = true; // 标记接收完成
        }
    }
}

// 基于WS的流式合成
async function getTtsChunkWavWS(item:any) {
  // console.log('停止监听')
  ws2.removeEventListener('message',handleMessage)
  // 根据mId唯一确定是哪条语音
  if(ws2.readyState !== ws2.OPEN) {
    Message.error("websocket 链接失败，请检查 Websocket 后端服务是否正确开启")
    return
  }
  // console.log('开始监听')
  ws2.addEventListener('message', handleMessage)
  
  // 默认audioplay为false，即未有语音正在播放
  if(!audioplay.value) {
    audioplay.value = true
    if(item.content) {
      ws2.send(item.content)    
    }
  } else 
  //audioplay为true,即语音正在播放中,现在暂停此前的并播放当前点击的那条语音
  {
    audioplay.value = false
    // console.log('2')
    audioSrcNodes.value.forEach(node => {
      // console.log(node)
      node.stop(); // 停止每个音频源节点以暂停播放
      node.disconnect()
    });
    // console.log(audioSrcNodes.value.length,"###")
    audioSrcNodes.value = [];
    // console.log('节点：',audioSrcNodes.value)
    // mId.value = item.mId    如何此时停止上一个监听   开始新的监听？？？
    audioplay.value = true
    if(item.content) {
      ws2.send(item.content)
    }
  }
}
</script>



<style lang="less">
h4 {
  margin-bottom: 2px;
}

.logodisplay {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.infofont {
  color: #606266;
  font-family: 'deyi';
  font-weight: 600;
  font-size: 1.4rem;
  line-height: 3rem;
  letter-spacing: 1px;
}

.infoefont {
  color: #606266;
  font-family: 'deyi';

}

.source:hover {
  cursor: pointer;
}

/* span {
  margin-left: 0;
} */

:deep(.md-editor-preview default-theme) {
  padding-left: 10px;
}

:deep(.md-editor-preview-wrapper) {
  padding-left: 30px;
  padding-right: 20px;
  padding-top: 0;
  padding-bottom: 0;
}

.custom-md-preview div {
  padding: 0 !important; 
}

.md-editor-preview-wrapper {
  padding: 0;
}


.knowledegelabel {
  display: flex;
}


.source {
  // padding-left: 17px;
  padding-bottom: 8px;
}

.app {
  background: linear-gradient(180deg, #f5f4f6, #e6ebf7);
  align-content: center;
  height: calc(100vh - 48px);
  display: flex;
  align-content: center;
  align-items: center;
  flex-direction: column;
  overflow: hidden;
}

.selectKnowledge {
  // padding-bottom: 4px;
  padding: 0;
  height: 50px;
  display: flex;
  align-items: center;
  justify-content: left;
  margin: 0 10px;
  // border-bottom: 1px solid #D2D3E3;
}

.selectdiv {
  height: 50px;
  display: flex;
  align-items: center;
}

.checkbox {
  width: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #EEF1FF;
  height: 40px;
  border-radius: 8px;
  margin-right: 6px;
  .fontstyle {
    // padding-left: 5px;
    font-size: 15px;
    color: #615ced;
  }
}

.selectmatch .ant-select-focused.ant-select:not(.ant-select-disabled).ant-select-focused .ant-select-selector {
    border-color: #EEF1FF !important; /* 设置选中时边框颜色为透明色 */
    box-shadow: none !important; 
  }

.selectmatch {
  font-size: 15px;
  width: 250px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #615ced;
  background-color: #EEF1FF;
  height: 40px;
  border-radius: 8px;
  .selectstyle {
    border-color: #EEF1FF !important; /* 设置选中时边框颜色为透明色 */
    box-shadow: none !important; 
    padding-left: 5px;
    width: 150px;
  }
}

.arco-scrollbar-container {
  width: 100%;

}

.chat-messages {
  display: flex;
  width: 100%;
  margin-top: 10px;
  margin-bottom: 5px;
  padding: 0;
  height: calc(100vh - 100px);
  /* background-color: #0f0; */
  overflow: hidden;
}

.arco-scrollbar {
  width: 100%;
  display: flex;
  align-content: center;
  align-items: center;
}

.chatarr {
  width: 100%;
  display: flex;
  justify-content: center;

  /* background-color: #000; */
  // 回复的样式
  .reply0 {
    display: flex;
    width: 70%;
    margin-top: 20px;
    // align-items: center;

    .contentmessage {
      width: 100%;
      margin-left: 10px;
      padding-left: 15px;
      padding-right: 15px;
      padding-top: 15px;
      padding-bottom: 15px;
      line-height: 1.5;
      font-weight: 400;
      /* box-shadow:
                9.2px 9px 3.6px rgba(0, 128, 255, 0.005),
                18.5px 18px 10px rgba(0, 128, 255, 0.012),
                31.3px 30.5px 24.1px rgba(0, 128, 255, 0.021),
            ; */
      border-radius: 8px;
      box-shadow: 0 16px 20px 0 rgba(174, 167, 223, .06);
      // max-width: 500px;
      // border: 1px solid #000;
      border-radius: 8px;
      color: #000;
      background-color: #fff;
      word-wrap: break-word;
    }
  }

  .reply1 {
    display: flex;
    width: 70%;
    margin-top: 20px;
    // align-items: center;

    .contentmessage {
      color: #05073b;
      font-size: 15px;
      line-height: 24px;
      line-height: 1.5;
      // margin-left: 2px;
      padding: 15px;
      width: 100%;
      border: none;
      color: #000;
      word-wrap: break-word;
    }
  }
}

.avatar img {
  border-radius: 50%;
  /* 这将设置图像的圆角为 50%，使其成为一个完整的圆形 */
}

.chat-input {
  // padding-top: 5px;
  padding-bottom: 10px;
  height: auto;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.coalesce {
  background-color: #fff;
  border-radius: 15px;
  position: relative;
  width: 70%;
  height: auto;
  display: flex;
  flex-direction: column;
  // 左右居中
  align-items: left;
}

.audio-button {
  background-color: transparent !important;
  border: 1px solid transparent !important;
  position: absolute;
  border-radius: 15px;
  right: 55px;
  bottom: 7px;
  width: 50px;
  height: 50px;
}

.send-button {
  background-color: transparent !important;
  border: 1px solid transparent !important;
  position: absolute;
  border-radius: 15px;
  right: 7px;
  bottom: 7px;
  width: 50px;
  height: 50px;
}

.textarea {
  height: auto;
  width: 100%;
  // padding-bottom: 20px;
  background-color: #fff;
  box-shadow: #f5f4f6;
  border-radius: 15px;
  border:none;
  border-color: transparent;
  outline: none;
  // overflow: hidden;
}

.textarea:focus {
  border-color: transparent !important;
  box-shadow: none !important; 
}


textarea::-webkit-scrollbar {
  display: none;
}

button {
  width: 60px;
  height: 50px;
  margin-left: 15px;
  padding: 8px;
  background-color: #1890FF;
  color: #fff;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  outline: none;
}

div::-webkit-scrollbar {
  width: 10px;
}

/* 滑块 */
div::-webkit-scrollbar-thumb {
  border-radius: 25px;
  background-color: #C1C1C1
}

/* 轨道 */
div::-webkit-scrollbar-track {
  background-color: #EFECF6;
}
</style>
  